﻿Imports System
Imports System.Collections
Imports System.ComponentModel
Imports System.ComponentModel.Design
Imports System.Globalization

Namespace System.Workflow.ComponentModel.Design
    Friend NotInheritable Class ReferenceService
        Implements IReferenceService, IDisposable
        ' Methods
        Friend Sub New(ByVal provider As IServiceProvider)
            Me.provider = provider
        End Sub

        Private Sub CreateReferences(ByVal component As IComponent)
            Me.CreateReferences(String.Empty, component, component)
        End Sub

        Private Sub CreateReferences(ByVal trailingName As String, ByVal reference As Object, ByVal sitedComponent As IComponent)
            If Not Object.ReferenceEquals(reference, Nothing) Then
                Me.references.Add(New ReferenceHolder(trailingName, reference, sitedComponent))
                Dim descriptor As PropertyDescriptor
                For Each descriptor In TypeDescriptor.GetProperties(reference, ReferenceService.Attributes)
                    Dim obj2 As Object = Nothing
                    Try 
                        obj2 = descriptor.GetValue(reference)
                    Catch obj1 As Object
                    End Try
                    If (Not obj2 Is Nothing) Then
                        Dim customAttributes As BrowsableAttribute() = DirectCast(obj2.GetType.GetCustomAttributes(GetType(BrowsableAttribute), True), BrowsableAttribute())
                        If ((customAttributes.Length > 0) AndAlso customAttributes(0).Browsable) Then
                            Me.CreateReferences(String.Format(CultureInfo.InvariantCulture, "{0}.{1}", New Object() { trailingName, descriptor.Name }), descriptor.GetValue(reference), sitedComponent)
                        End If
                    End If
                Next
            End If
        End Sub

        Private Sub Dispose(ByVal disposing As Boolean)
            If ((Not Me.references Is Nothing) AndAlso (Not Me.provider Is Nothing)) Then
                Dim service As IComponentChangeService = [TryCast](Me.provider.GetService(GetType(IComponentChangeService)),IComponentChangeService)
                If (Not service Is Nothing) Then
                    RemoveHandler service.ComponentAdded, New ComponentEventHandler(AddressOf Me.OnComponentAdded)
                    RemoveHandler service.ComponentRemoved, New ComponentEventHandler(AddressOf Me.OnComponentRemoved)
                    RemoveHandler service.ComponentRename, New ComponentRenameEventHandler(AddressOf Me.OnComponentRename)
                    RemoveHandler service.ComponentChanged, New ComponentChangedEventHandler(AddressOf Me.OnComponentChanged)
                End If
                RemoveHandler TypeDescriptor.Refreshed, New RefreshEventHandler(AddressOf Me.OnComponentRefreshed)
                Me.references = Nothing
                Me.provider = Nothing
            End If
        End Sub

        Private Sub EnsureReferences()
            If (Me.references Is Nothing) Then
                If (Me.provider Is Nothing) Then
                    Throw New ObjectDisposedException("IReferenceService")
                End If
                Dim service As IComponentChangeService = [TryCast](Me.provider.GetService(GetType(IComponentChangeService)),IComponentChangeService)
                If (Not service Is Nothing) Then
                    AddHandler service.ComponentAdded, New ComponentEventHandler(AddressOf Me.OnComponentAdded)
                    AddHandler service.ComponentRemoved, New ComponentEventHandler(AddressOf Me.OnComponentRemoved)
                    AddHandler service.ComponentRename, New ComponentRenameEventHandler(AddressOf Me.OnComponentRename)
                    AddHandler service.ComponentChanged, New ComponentChangedEventHandler(AddressOf Me.OnComponentChanged)
                End If
                AddHandler TypeDescriptor.Refreshed, New RefreshEventHandler(AddressOf Me.OnComponentRefreshed)
                Dim container As IContainer = [TryCast](Me.provider.GetService(GetType(IContainer)),IContainer)
                If (container Is Nothing) Then
                    Throw New InvalidOperationException
                End If
                Me.references = New ArrayList(container.Components.Count)
                Dim component As IComponent
                For Each component In container.Components
                    Me.CreateReferences(component)
                Next
            Else
                If ((Not Me.addedComponents Is Nothing) AndAlso (Me.addedComponents.Count > 0)) Then
                    Dim list As New ArrayList(Me.addedComponents)
                    Dim component2 As IComponent
                    For Each component2 In list
                        Me.RemoveReferences(component2)
                        Me.CreateReferences(component2)
                    Next
                    Me.addedComponents.Clear
                End If
                If ((Not Me.removedComponents Is Nothing) AndAlso (Me.removedComponents.Count > 0)) Then
                    Dim list2 As New ArrayList(Me.removedComponents)
                    Dim component3 As IComponent
                    For Each component3 In list2
                        Me.RemoveReferences(component3)
                    Next
                    Me.removedComponents.Clear
                End If
                If ((Not Me.changedComponents Is Nothing) AndAlso (Me.changedComponents.Count > 0)) Then
                    Dim list3 As New ArrayList(Me.changedComponents)
                    Dim component4 As IComponent
                    For Each component4 In list3
                        Me.RemoveReferences(component4)
                        Me.CreateReferences(component4)
                    Next
                    Me.changedComponents.Clear
                End If
            End If
        End Sub

        Protected Overrides Sub Finalize()
            Try 
                Me.Dispose(False)
            Finally
                MyBase.Finalize
            End Try
        End Sub

        Private Sub OnComponentAdded(ByVal sender As Object, ByVal cevent As ComponentEventArgs)
            If (Me.addedComponents Is Nothing) Then
                Me.addedComponents = New ArrayList
            End If
            Me.addedComponents.Add(cevent.Component)
            If (Not Me.removedComponents Is Nothing) Then
                Me.removedComponents.Remove(cevent.Component)
            End If
            If (Not Me.changedComponents Is Nothing) Then
                Me.changedComponents.Remove(cevent.Component)
            End If
        End Sub

        Private Sub OnComponentChanged(ByVal sender As Object, ByVal cevent As ComponentChangedEventArgs)
            Dim item As IComponent = DirectCast(Me, IReferenceService).GetComponent(cevent.Component)
            If (((Not item Is Nothing) AndAlso ((Me.addedComponents Is Nothing) OrElse Not Me.addedComponents.Contains(item))) AndAlso ((Me.removedComponents Is Nothing) OrElse Not Me.removedComponents.Contains(item))) Then
                If (Me.changedComponents Is Nothing) Then
                    Me.changedComponents = New ArrayList
                    Me.changedComponents.Add(item)
                ElseIf Not Me.changedComponents.Contains(item) Then
                    Me.changedComponents.Add(item)
                End If
            End If
        End Sub

        Private Sub OnComponentRefreshed(ByVal e As RefreshEventArgs)
            If (Not e.ComponentChanged Is Nothing) Then
                Me.OnComponentChanged(Me, New ComponentChangedEventArgs(e.ComponentChanged, Nothing, Nothing, Nothing))
            End If
        End Sub

        Private Sub OnComponentRemoved(ByVal sender As Object, ByVal cevent As ComponentEventArgs)
            If (Me.removedComponents Is Nothing) Then
                Me.removedComponents = New ArrayList
            End If
            Me.removedComponents.Add(cevent.Component)
            If (Not Me.addedComponents Is Nothing) Then
                Me.addedComponents.Remove(cevent.Component)
            End If
            If (Not Me.changedComponents Is Nothing) Then
                Me.changedComponents.Remove(cevent.Component)
            End If
        End Sub

        Private Sub OnComponentRename(ByVal sender As Object, ByVal cevent As ComponentRenameEventArgs)
            Dim holder As ReferenceHolder
            For Each holder In Me.references
                If Object.ReferenceEquals(holder.SitedComponent, cevent.Component) Then
                    holder.ResetName
                    Exit For
                End If
            Next
        End Sub

        Private Sub RemoveReferences(ByVal component As IComponent)
            If (Not Me.references Is Nothing) Then
                Dim i As Integer = (Me.references.Count - 1)
                Do While (i >= 0)
                    If Object.ReferenceEquals(DirectCast(Me.references.Item(i), ReferenceHolder).SitedComponent, component) Then
                        Me.references.RemoveAt(i)
                    End If
                    i -= 1
                Loop
            End If
        End Sub

        Private Function System.ComponentModel.Design.IReferenceService.GetComponent(ByVal reference As Object) As IComponent Implements IReferenceService.GetComponent
            If Object.ReferenceEquals(reference, Nothing) Then
                Throw New ArgumentNullException("reference")
            End If
            Me.EnsureReferences
            Dim holder As ReferenceHolder
            For Each holder In Me.references
                If Object.ReferenceEquals(holder.Reference, reference) Then
                    Return holder.SitedComponent
                End If
            Next
            Return Nothing
        End Function

        Private Function System.ComponentModel.Design.IReferenceService.GetName(ByVal reference As Object) As String Implements IReferenceService.GetName
            If Object.ReferenceEquals(reference, Nothing) Then
                Throw New ArgumentNullException("reference")
            End If
            Me.EnsureReferences
            Dim holder As ReferenceHolder
            For Each holder In Me.references
                If Object.ReferenceEquals(holder.Reference, reference) Then
                    Return holder.Name
                End If
            Next
            Return Nothing
        End Function

        Private Function System.ComponentModel.Design.IReferenceService.GetReference(ByVal name As String) As Object Implements IReferenceService.GetReference
            If (name Is Nothing) Then
                Throw New ArgumentNullException("name")
            End If
            Me.EnsureReferences
            Dim holder As ReferenceHolder
            For Each holder In Me.references
                If String.Equals(holder.Name, name, StringComparison.OrdinalIgnoreCase) Then
                    Return holder.Reference
                End If
            Next
            Return Nothing
        End Function

        Private Function System.ComponentModel.Design.IReferenceService.GetReferences() As Object() Implements IReferenceService.GetReferences
            Me.EnsureReferences
            Dim objArray As Object() = New Object(Me.references.Count  - 1) {}
            Dim i As Integer
            For i = 0 To objArray.Length - 1
                objArray(i) = DirectCast(Me.references.Item(i), ReferenceHolder).Reference
            Next i
            Return objArray
        End Function

        Private Function System.ComponentModel.Design.IReferenceService.GetReferences(ByVal baseType As Type) As Object() Implements IReferenceService.GetReferences
            If (baseType Is Nothing) Then
                Throw New ArgumentNullException("baseType")
            End If
            Me.EnsureReferences
            Dim list As New ArrayList(Me.references.Count)
            Dim holder As ReferenceHolder
            For Each holder In Me.references
                Dim reference As Object = holder.Reference
                If baseType.IsAssignableFrom(reference.GetType) Then
                    list.Add(reference)
                End If
            Next
            Dim array As Object() = New Object(list.Count  - 1) {}
            list.CopyTo(array, 0)
            Return array
        End Function

        Private Sub System.IDisposable.Dispose() Implements IDisposable.Dispose
            Me.Dispose(True)
            GC.SuppressFinalize(Me)
        End Sub


        ' Fields
        Private addedComponents As ArrayList
        Private Shared ReadOnly Attributes As Attribute() = New Attribute() { BrowsableAttribute.Yes }
        Private changedComponents As ArrayList
        Private provider As IServiceProvider
        Private references As ArrayList
        Private removedComponents As ArrayList

        ' Nested Types
        Private NotInheritable Class ReferenceHolder
            ' Methods
            Friend Sub New(ByVal trailingName As String, ByVal reference As Object, ByVal sitedComponent As IComponent)
                Me.trailingName = trailingName
                Me.reference = reference
                Me.sitedComponent = sitedComponent
            End Sub

            Friend Sub ResetName()
                Me.fullName = Nothing
            End Sub


            ' Properties
            Friend ReadOnly Property Name As String
                Get
                    If (Me.fullName Is Nothing) Then
                        If (((Not Me.sitedComponent Is Nothing) AndAlso (Not Me.sitedComponent.Site Is Nothing)) AndAlso (Not Me.sitedComponent.Site.Name Is Nothing)) Then
                            Me.fullName = String.Format(CultureInfo.InvariantCulture, "{0}{1}", New Object() { Me.sitedComponent.Site.Name, Me.trailingName })
                        Else
                            Me.fullName = String.Empty
                        End If
                    End If
                    Return Me.fullName
                End Get
            End Property

            Friend ReadOnly Property Reference As Object
                Get
                    Return Me.reference
                End Get
            End Property

            Friend ReadOnly Property SitedComponent As IComponent
                Get
                    Return Me.sitedComponent
                End Get
            End Property


            ' Fields
            Private fullName As String
            Private reference As Object
            Private sitedComponent As IComponent
            Private trailingName As String
        End Class
    End Class
End Namespace

